#include "stdlib.h"
#include "string.h"
#include <stdio.h>
#include <netinet/in.h>
#include "simclist_impl.h"
#include "../ping.h"

int string_file_comparator(const void *a, const void *b) {
	const elem_file_t *alfa = (elem_file_t *) a;
	const elem_file_t *beta = (elem_file_t *) b;
	if (strcmp(alfa->nome, beta->nome) > 0)
		return -1;
	if (strcmp(alfa->nome, beta->nome) < 0)
		return 1;
	return 0;
}

int ping_comparator(const void *a, const void *b) {
	const elem_ip_ping_t *alfa = (elem_ip_ping_t *) a;
	const elem_ip_ping_t *beta = (elem_ip_ping_t *) b;
	if (alfa->ping_time > beta->ping_time)
		return -1;
	if (alfa->ping_time < beta->ping_time)
		return 1;
	return 0;
}

int ip_comparator(const void *a, const void *b) {
	const elem_superpeer_t *alfa = (elem_superpeer_t *) a;
	const elem_superpeer_t *beta = (elem_superpeer_t *) b;
	if (alfa->ip > beta->ip)
		return -1;
	if (alfa->ip < beta->ip)
		return 1;
	return 0;
}

int peer_comparator(const void *a, const void *b) {
	const elem_superpeer_t *alfa = (elem_superpeer_t *) a;
	const elem_superpeer_t *beta = (elem_superpeer_t *) b;
	if (alfa->peer_number > beta->peer_number)
		return -1;
	if (alfa->peer_number < beta->peer_number)
		return 1;
	return 0;
}

int string_file_seeker(const void *a, const void *b) {
	if (a == NULL || b == NULL)
		return 0;
	const elem_file_t *alfa = (elem_file_t *) a;
	const char *beta = (char *) b;
	if (strlen(alfa->nome)!=strlen(beta)) return 0;
	if (strcmp(alfa->nome, beta) == 0)
		return 1;
	else
		return 0;
}

int string_seeker(const void *a, const void *b) {
	if (a == NULL || b == NULL)
		return -1;
	const char *alfa = (char *) a;
	const char *beta = (char *) b;
	if (strcmp(alfa, beta) == 0)
		return 1;
	else
		return 0;
}

int superpeer_ip_seeker(const void *a, const void *b) {
	if (a == NULL || b == NULL)
		return -1;
	const elem_superpeer_t *alfa = (elem_superpeer_t *) a;
	const in_addr_t *beta = (in_addr_t *) b;
	if (alfa->ip == *beta)
		return 1;
	return 0;
}

int ip_file_seeker(const void *a, const void *b) {
	if (a == NULL || b == NULL)
		return -1;
	const in_addr_t *alfa = (in_addr_t *) a;
	const in_addr_t *beta = (in_addr_t *) b;
	if (*alfa == *beta)
		return 1;
	return 0;
}

void *element_serializer_superpeer_list(const void *restrict el,
		uint32_t *restrict serializ_len) {
	const elem_superpeer_t *alfa = (elem_superpeer_t *) el;
	*serializ_len = 12;
	void *buffer = malloc(12);
	memcpy(buffer, alfa, 12);
	return buffer;
}

void *element_unserializer_superpeer_list(const void *restrict data,
		uint32_t *restrict data_len) {

	const elem_superpeer_t *alfa = malloc(12);
	memcpy(alfa, data, 12);
	return alfa;
}

void *element_serializer_peer_list(const void *restrict el,
		uint32_t *restrict serializ_len) {
	const in_addr_t *alfa = (in_addr_t *) el;
	*serializ_len = 12;
	void *buffer = malloc(12);
	memcpy(buffer, alfa, 12);
	return buffer;
}

void *element_unserializer_peer_list(const void *restrict data,
		uint32_t *restrict data_len) {
	const in_addr_t *alfa = malloc(12);
	memcpy(alfa, data, 12);
	return alfa;
}

void *element_serializer_string(const void *restrict el,
		uint32_t *restrict serializ_len) {
	const char *alfa = (char *) el;
	*serializ_len = (50);
	void *buffer = malloc(50);
	memcpy(buffer, alfa, *serializ_len);
	return buffer;
}

void *element_unserializer_string(const void *restrict data,
		uint32_t *restrict data_len) {
	const char *alfa = malloc(50);
	memcpy(alfa, data, *data_len);
	return alfa;
}

size_t list_superpeer_meter(const void *el) {
	return 30;
}

size_t list_file_meter(const void *el) {
	const elem_file_t *alfa = (elem_file_t *) el;
	size_t size = sizeof(alfa->lista_peer) + 100;
	return size;
}

void add_file(list_t *list, char *nome, in_addr_t ip) {
	elem_file_t *elem_seek = (elem_file_t*) list_seek(list, nome); // trovo l'elemento in base alla funzione string_seeker
	if (elem_seek == NULL) {
		elem_file_t el;
		el.nome = nome;

		list_t *lista_peer = malloc(sizeof(list_t));
		if (list_init(lista_peer) == -1) {
			perror("ERRORE");
			fflush(stdout);
		}
		list_attributes_comparator(lista_peer, list_comparator_uint32_t);
		list_attributes_serializer(lista_peer, element_serializer_peer_list);
		list_attributes_copy(lista_peer, list_meter_uint32_t, 1);
		list_attributes_seeker(lista_peer, ip_file_seeker);

		if (list_append(lista_peer, &ip) < 0) {
			perror("ERRORE IN LISTA (append IP)\n");
			return;
		}

		el.lista_peer = lista_peer;
		list_sort(list, 1); // ogni inserimento mantiene la lista ordinata

		if (list_append(list, &el) < 0) {
			perror("ERRORE IN LISTA (append ELEMENT)\n");
			return;
		}
		return;
	} else {
		list_t *peer_list = elem_seek->lista_peer;
		in_addr_t *ip_seek = (in_addr_t*) list_seek(peer_list, &ip);
		if (ip_seek == NULL) { // ip già registrato nell'avere il file una
			if (list_append(peer_list, &ip) < 0) {
				perror("ERRORE IN LISTA(append IP già pres)\n");
			}
			list_sort(peer_list, 1); // ogni inserimento mantiene la sottolista dei peer ordinata
		}
	}
	return;
}

void delete_file(list_t *list, char *nome, in_addr_t ip) {
	elem_file_t *elem_seek = (elem_file_t*) list_seek(list, nome); // trovo l'elemento in base alla funzione string_seeker
	if (elem_seek != NULL) {
		list_t *peer_list = elem_seek->lista_peer;
		in_addr_t *ip_seek = (in_addr_t*) list_seek(peer_list, &ip);
		if (ip_seek != NULL) {
			list_delete(peer_list, ip_seek);
			list_sort(peer_list, 1);
			if (list_size(peer_list) == 0)
				list_delete(list, elem_seek);
		}
	}
	return;
}

void delete_peer_from_list(list_t *list, in_addr_t del_ip) {
	list_iterator_start(list);
	while (list_iterator_hasnext(list)) {
		elem_file_t *e = ((elem_file_t *) (list_iterator_next(list)));
		list_t *lista_peer = e->lista_peer;
		list_iterator_start(lista_peer);
		while (list_iterator_hasnext(lista_peer)) {
			in_addr_t *curr_ip =
					((in_addr_t *) (list_iterator_next(lista_peer)));
			if (curr_ip == del_ip) {
				list_delete(list, curr_ip);
				if (list_size(list) > 0)
					list_sort(list, 1);
			}
		}
	}
}

void print_file_list(list_t *list) {
	list_iterator_start(list);
	while (list_iterator_hasnext(list)) {
		elem_file_t *e = ((elem_file_t *) (list_iterator_next(list)));
		printf("%s", e->nome);
		printf(" ---> ");
		fflush(stdout);
		list_t *lista_peer = e->lista_peer;
		list_iterator_start(lista_peer);
		while (list_iterator_hasnext(lista_peer)) {
			in_addr_t *ip = ((in_addr_t *) (list_iterator_next(lista_peer)));
			struct in_addr e;
			e.s_addr = *ip;
			printf("%s   ", inet_ntoa(e));
			fflush(stdout);
		}
		list_iterator_stop(lista_peer);
		printf("\n");
	}
	list_iterator_stop(list);
	return;
}

void add_superpeer(list_t* list, in_addr_t ip) {
	elem_superpeer_t *elem_seek = (elem_superpeer_t*) list_seek(list, &ip);
	if (elem_seek == NULL) { // la lista non contiene duplicati
		elem_superpeer_t elem;
		elem.ip = ip;
		elem.peer_number = 0; // nessun peer associato al momento della creazione
		list_append(list, &elem);
		list_sort(list, 1);
	}
	return;
}

void delete_superpeer(list_t* list, in_addr_t ip) {
	elem_superpeer_t *elem_seek = (elem_superpeer_t*) list_seek(list, &ip);
	if (elem_seek != NULL) {
		list_delete(list, elem_seek);
		list_sort(list, 1);
	}
	return;
}

void add_peer(list_t* list, in_addr_t ip) {
	in_addr_t *elem_seek = (in_addr_t*) list_seek(list, &ip);
	if (elem_seek == NULL) { // la lista non contiene duplicati
		in_addr_t elem = ip;
		list_append(list, &elem);
		list_sort(list, 1);
	}
	return;
}

void delete_peer(list_t* list, in_addr_t ip) {
	in_addr_t *elem_seek = (in_addr_t*) list_seek(list, &ip);
	if (elem_seek != NULL) {
		list_delete(list, elem_seek);
		list_sort(list, 1);
	}
	return;
}

void update_peer_number(list_t* list, in_addr_t ip, int peer_number) {
	elem_superpeer_t *elem_seek = list_seek(list, &ip);
	if (elem_seek != NULL) {
		elem_seek->peer_number = peer_number; // aggiorno numero di peer
		list_sort(list, 1); // ripristino l'ordine nella lista
	}
	return;
}

list_t *get_peer_list(list_t *list, char *nome) { // questa funzione restituisce la lista degli ip che hanno il file richiesto
	elem_file_t *elem_seek = list_seek(list, nome);
	if (elem_seek == NULL)
		return NULL;
	return elem_seek->lista_peer;
}

void print_superpeer_list(list_t *list) {
	struct in_addr in;
	list_attributes_comparator(list, peer_comparator);
	list_sort(list, 1);
	list_attributes_comparator(list, ip_comparator);
	list_iterator_start(list);
	while (list_iterator_hasnext(list)) {
		elem_superpeer_t *e = ((elem_superpeer_t *) (list_iterator_next(list)));
		in.s_addr = e->ip;
		printf("%s -->  ", inet_ntoa(in));
		printf("%d\n", e->peer_number);
	}
	list_iterator_stop(list);
	return;
}

void print_peer_list(list_t *list) {
	struct in_addr in;
	list_attributes_comparator(list, peer_comparator);
	list_sort(list, 1);
	list_attributes_comparator(list, ip_comparator);
	list_iterator_start(list);
	while (list_iterator_hasnext(list)) {
		in_addr_t *e = ((in_addr_t *) (list_iterator_next(list)));
		in.s_addr = *e;
		printf("%s", inet_ntoa(in));
	}
	list_iterator_stop(list);
	return;
}

list_t *list_sort_by_ping(list_t *list) {
	list_t *ping_list = malloc(sizeof(list_t));
	list_init(ping_list);
	list_attributes_comparator(ping_list, ping_comparator); // ping comparator per ordinare la lista

	list_iterator_start(list);
	while (list_iterator_hasnext(list)) {
		elem_superpeer_t *e = ((elem_superpeer_t *) (list_iterator_next(list)));
		elem_ip_ping_t node;
		node.ip = e->ip;
		node.ping_time = ping_ip_time(e->ip);
		list_append(ping_list, &node);
		list_sort(ping_list, 1); // ordine crescente per ping time
	}
	list_iterator_stop(list);
	return ping_list;
}

void list_superpeer_copy(list_t *src, list_t *dest) {

	list_iterator_start(src);

	while (list_iterator_hasnext(src)) {

		elem_superpeer_t *current = list_iterator_next(src);

		add_superpeer(dest, current->ip);

	}

	list_iterator_stop(src);

}

void list_superpeer_range_copy(list_t *src, list_t *dest,int from , int to) {

	list_iterator_start(src);

	int pos=0;
	while (list_iterator_hasnext(src)) {

		elem_superpeer_t *current = list_iterator_next(src);
		if(pos<=to && pos>=from)
		add_superpeer(dest, current->ip);

	}

	list_iterator_stop(src);

}

void list_peer_copy(list_t *src, list_t*dest) {

	list_iterator_start(src);

	while (list_iterator_hasnext(src)) {

		in_addr_t *current = list_iterator_next(src);

		list_append(dest, current);

	}

	list_iterator_stop(src);

}

